//==============================================================================
#include "CApp.h"
#include "Character.h"
#include "Obstacle.h"
#include "Behavior/AllBehavior.h"
#include "setBehavior.h"
#include "Vectores.h"
#include "Graph.h"
#include "Triangle.h"
#include "Player.h"
#include "Pikmen.h"
#include "PikmenEnemy.h"
#include "Nave.h"
#include "Grito.h"

#include "Monitor.h"

#include <stdlib.h>
#include <iostream>
#include <stdio.h>
#include <algorithm>

#include <SDL/SDL.h>
#include <SDL/SDL_gfxPrimitives.h>

using namespace std;

vector<Triangle *> triangles;
Graph graph;

// DEBUG
float myCurrent[3];

//==============================================================================
CApp::CApp() {
    Surf_Display = NULL;
    /*
     * Velocidad de pintado de la pantalla.
     * Lo usual suele ser 60 frames/seg === 16.6666 milliseg/frame
     */
    framesPerSecond = 16;
    old_time = 0;
    Running = true;
    // Inicializamos los Flags
    _flag_showGraph = false;
    _flag_showOlor = false;
    _flag_showSonido = false;
}

bool CApp::OnInit() {
  FILE *myFile = NULL;        // File descriptor
  char *line = NULL;          // Buffer de línea
  size_t nline = 0;
  int x1, y1, x2, y2, x3, y3,tipo;
  int z1, w, h;
  int ajusteX = 0, ajusteY = 0;
  float point[3];

  if(SDL_Init(SDL_INIT_EVERYTHING) < 0) {
      return false;
  }

  if( !(myFile = fopen(f_name, "r")) ) {
    fprintf(stderr, "Hubo un problema con el archivo \"%s\" y no pudo abrirse. \n", f_name);
        exit(EXIT_FAILURE);
  }

  // Leemos el archivo de mapa
  while( getline(&line, &nline, myFile) != -1 ) {
    if( sscanf(line, "screen %d %d",
          &TAM_VENTANA_X, &TAM_VENTANA_Y) ){
      printf("Tamano de la camara: %dx%d\n",
          TAM_VENTANA_X, TAM_VENTANA_Y);
      if((Surf_Display = SDL_SetVideoMode(
          TAM_VENTANA_X,TAM_VENTANA_Y,
          32,
          SDL_HWSURFACE | SDL_DOUBLEBUF)) == NULL) {
        return false;
      }
      if((Surf_BG = CSurface::OnLoad("images/bgPrincipal.bmp")) == NULL) {
        return false;
      }
      if((Surf_heal = CSurface::OnLoad("images/heal.bmp")) == NULL) {
        return false;
      }
      CSurface::Transparent(Surf_heal, 0, 183, 239);
      m.OnInit();
    } else if( sscanf(line, "ajuste %d %d",
          &ajusteX, &ajusteY) ){
      printf("  + Trasladando coordenadas en ($%d, %d)\n",
          ajusteX, ajusteY);
    } else if( sscanf(line, "map %d %d",
          &TAM_MAX_VENTANA_X, &TAM_MAX_VENTANA_Y) ){
      TAM_MAX_VENTANA_X += ajusteX*2;
      TAM_MAX_VENTANA_Y += ajusteY*2;
      printf("  + Tamano del mapa: %dx%d\n",
          TAM_MAX_VENTANA_X, TAM_MAX_VENTANA_Y);
    } else if( sscanf(line, "triangle %d %d %d %d %d %d",
          &x1, &y1, &x2, &y2, &x3, &y3) ){
      printf("  + Creando triangulo: (%d %d, %d %d, %d %d)\n",
          x1, y1, x2, y2, x3, y3);
      triangles.push_back(new Triangle(
          x1+ajusteX, y1+ajusteY,
          x2+ajusteX, y2+ajusteY,
          x3+ajusteX, y3+ajusteY));
    } else if( sscanf(line, "obstacle %d %d %d %d %d",
          &x1, &y1, &z1, &w, &h) ){
      printf("  + Creando obstaculo: (%d %d %d %d %d)\n",
          x1, y1, z1, w, h);
      obstacle.push_back(new ObstacleMuro(
            x1+ajusteX, y1+ajusteY, z1, w, h));
    } else if( sscanf(line, "volcan %d %d %d %d %d",
          &x1, &y1, &z1, &w, &h) ){
      printf("  + Creando volcan: (%d %d %d %d %d)\n",
          x1, y1, z1, w, h);
      obstacle.push_back(new ObstacleVolcan(
            x1+ajusteX, y1+ajusteY, z1, w, h));
    } else if( sscanf(line, "plataform %f %f %f %d %d",
          point, point+1, point+2, &w, &h) ){
      printf("  + Creando plataforma: (%f %f %f %d %d)\n",
          point[0], point[1], point[2], w, h);
      point[0] += ajusteX;
      point[1] += ajusteY;
      m.addPlatform(point, w, h);
    } else if( sscanf(line, "player %d %d", &x1, &y1) ){
      printf("  + Colcando al player en %d %d\n", x1, y1);
      g_player_x = x1+ajusteX;
      g_player_y = y1+ajusteY;
    } else if( sscanf(line, "build %d", &x1) ){
      if(x1) buildGraphFromTriangles();
    } else if( sscanf(line, "edge %d %d", &x1, &y1) ){
      printf("  + Agregando vertice (%d <=> %d) (%d)\n",
          x1, y1, int(graph.nodes.size()) );
      graph.addDoubleEdge(x1, y1);
    } else if( sscanf(line, "heal %d %d", &x1, &y1) ){
      printf("  + Agregando punto sanacion %d %d\n",
          x1, y1);
      Node* node = new Node(x1+ajusteX, y1+ajusteY);
      graph_heal.addNode(node);
    } else if( sscanf(line, "cover %d %d", &x1, &y1) ){
      printf("  + Agregando punto sanacion %d %d\n",
          x1, y1);
      Node* node = new Node(x1+ajusteX, y1+ajusteY);
      graph_cover.addNode(node);
    } else if( sscanf(line, "pikmen %d %d", &x1, &y1) ){
      printf("  + Agregando pikmen en %d %d\n",
          x1, y1);
      m.addPikmen(x1+ajusteX, y1+ajusteY);
    } else if( sscanf(line, "enemy %d %d", &x1, &y1) ){
      printf("  + Agregando enemy en %d %d\n",
          x1, y1);
      m.addEnemy(x1+ajusteX, y1+ajusteY);
    } else if( sscanf(line, "nave %d %d %d",&tipo, &x1, &y1) ){
      printf("  + Agregando nave 1 en %d %d, de tipo %d\n",
          x1, y1,tipo);
      float p[3];
      if (tipo == 0){
//      p[0]= 60;p[1]=82;p[2]=0;
//    }else if (tipo ==1){
//      p[0] =60 ;p[1]=119;p[2]=0;
        p[0]= 65;p[1]=88;p[2]=0;
      }else if (tipo ==1){
        p[0] =60 ;p[1]=126;p[2]=0;
      }else if (tipo ==2){
        p[0] =55;p[1]=200;p[2]=0;
      }
      float p1[3];
      p1[0] = x1+ajusteX;p1[1]=y1+ajusteY;p1[2]=0;
      m.naves.push_back(new Nave (p1,new Character(new Kinematic(p)),tipo));
    }
  }
  if(line) free(line);
  // Creamos los obstaculos del perimetro
  int ow = 62, oh = 70;
  for (int i=0 ; i*(ow-ow/4) - 8<TAM_MAX_VENTANA_X; i++)
    obstacle.push_back(new Obstacle(i*(ow-ow/4)-8,0,0, ow, oh));
  for (int i=1 ; (i-1)*oh<TAM_MAX_VENTANA_Y; i++)
    obstacle.push_back(new Obstacle(-8, i*(oh-oh/8),0, ow, oh));
  for (int i=1 ; (i-1)*oh<TAM_MAX_VENTANA_Y; i++)
    obstacle.push_back(new Obstacle(TAM_MAX_VENTANA_X-45, i*(oh-oh/8),0, ow, oh));
  for (int i=0 ; i*(ow-ow/4)-8<TAM_MAX_VENTANA_X; i++)
    obstacle.push_back(new Obstacle(i*(ow-ow/4)-8,TAM_MAX_VENTANA_Y-35,0, ow, oh));

  enemySep = m.GetEnemysApun();
  naves = m.naves;
  m.total=m.GetEnemys().size();
  m.actual = m.total;
  return true;
}


void CApp::OnLoop() {
  Uint32 current_time = SDL_GetTicks();
  m.isDeadAnyone();
  m.UpdateData(current_time,old_time);
//cout << "actual " << m.actual << endl;
  bool find = true;
  for (int i = 0,n=  naves.size();i<n && find; i++){
    find = naves[i]->nochange;
  }
  if (find){
    cout << endl
      << "*****************" << endl
      << "**   Ganaste **" << endl
      << "*****************" << endl << endl;
    exit(EXIT_SUCCESS);
  }

}

int CApp::OnExecute() {
    if(OnInit() == false) {
        return -1;
    }
    SDL_Event Event;
    Uint32 current_time;
    while(Running) {
        while(SDL_PollEvent(&Event)) {
            OnEvent(&Event);
        }
        current_time = SDL_GetTicks();
        if(current_time > old_time+framesPerSecond) {
          OnLoop();
          OnRender();
          old_time = current_time;
        }
    }
    OnCleanup();
    return 0;
}


void CApp::OnCleanup() {
  m.CleanUp();
  SDL_Quit();
}


void CApp::OnEvent(SDL_Event* Event) {
  CEvent::OnEvent(Event);
  m.GetPlayer()->OnEvent(Event);

}

void CApp::OnExit() {
  Running = false;
}

/*
 * Pinta obstaculos en el mapa
 */
void CApp::OnLButtonDown(int mX, int mY) {
  if(_flag_showCustomSonido){
    gritos.push_back(new Grito(mX-fixX(0), mY-fixY(0)));
  }
//cout << " " << mX - fixX(0) << ", " << mY - fixY(0) << ", ";
  cout.flush();
}

void CApp::OnRButtonDown(int mX, int mY){
    cout << endl;
    cout.flush();
}

/*
 *
 */
void CApp::OnMouseMove(int mX, int mY, int relX, int relY, bool Left,bool Right,bool Middle){
  if(Left) this->OnLButtonDown(mX, mY);
  else if (Right) this->OnRButtonDown(mX, mY);
  else if (Middle) this->OnMButtonDown(mX, mY);
}

void CApp::OnKeyDown(SDLKey sym, SDLMod mod, Uint16 unicode){
  vector<Character*> ch = m.GetCharacters();
  switch(sym){
      case SDLK_p:
        _flag_showGraph = !_flag_showGraph;
        break;
      case SDLK_o:
        _flag_showOlor = !_flag_showOlor;
        break;
      case SDLK_i:
        _flag_showSonido = !_flag_showSonido;
        break;
      case SDLK_u:
        _flag_showCustomSonido = !_flag_showCustomSonido;
        break;
      case SDLK_a:
        for(int i = 0, n = ch.size(); i <n ;i++){
          ch[i]->alejarDelPlayer();
        }
        break;
      default:
        break;
  }
  /*
  if (((int) '0' )<= sym && sym <= ((int) '9')){
    int selected= sym - 48;
    bool esta = false;
    for (int i = 0, n = enemy.size();!esta && i < n; i++){
      if (enemy[i] == characters[selected])
        esta = true;
    }
    if (!esta)
      enemy.push_back(characters[selected]);
  }else if ((int) 'w' == sym){
    while(!enemy.empty()){
      setBehavior *b= new setBehavior();
      b->addBehavior(new Wander(),enemy.back(),NULL);
      enemy.back()->addSetBehavior(b);
      enemy.pop_back();
    }
  }else if((int) 's' == sym){
    if (enemy.size() < 2){
      cout << "para hacer seek, primero introduce el numero de los characters , luego el target y luego presiona s." << endl;
    }else{
      Character* trgt = enemy.back();
      enemy.pop_back();
      while(!enemy.empty()){
        setBehavior *b= new setBehavior();
        b->addBehavior(new Seek(&obstacle),enemy.back(),trgt);
        enemy.back()->addSetBehavior(b);
        enemy.pop_back();
      }
    }
  }else if ((int) 'n' == sym){
     while(!enemy.empty()){
      setBehavior *b= new setBehavior();
      b->addBehavior(new Nothing(),enemy.back(),NULL);
      enemy.back()->addSetBehavior(b);
      enemy.pop_back();
    }
  }
  */
}

void CApp::OnRender() {
  int i, j, n, n2;
  vector<Obstacle*> obstacle = m.GetObstacle();
  vector<Character*> characters = m.GetCharacters(),enemy= m.GetEnemys();
  Character* player = m.GetPlayer();
  Kinematic* kine = NULL;

  vector<Node *>nodes;
  Node *node = NULL;
  int widthNode = 8;

  // Imagen de fondo
  SDL_FillRect(Surf_Display, NULL, SDL_MapRGB(Surf_Display->format, 0, 0, 0));
  for(i=0; i*Surf_BG->w<TAM_MAX_VENTANA_X; i++){
      for(j=0; j*Surf_BG->h<TAM_MAX_VENTANA_Y; j++){
          CSurface::OnDraw(Surf_Display, Surf_BG,
                  fixX(i*Surf_BG->w), fixY(j*Surf_BG->h));
      }
  }

  /// Obstaculos de arriba
  for(i=0, n=obstacle.size(); i<n; i++ )
    obstacle[i]->OnRender(Surf_Display);

  // WayPoints
  nodes = graph_heal.getNodes();
  for(i=0, n=nodes.size(); i<n; i++){
    node = nodes[i];

    CSurface::OnDraw(Surf_Display, Surf_heal,
        fixX((*node)[0] - Surf_heal->w/2),
        fixY((*node)[1] - Surf_heal->h/2));

  }
  if(theApp._flag_showGraph){
    nodes = graph_cover.getNodes();
    for(i=0, n=nodes.size(); i<n; i++){
      node = nodes[i];

      int myW = 20, myH = 12;
      filledEllipseRGBA(Surf_Display,
          fixX((*node)[0] - myW/2),
          fixY((*node)[1] - myH/2),
          myW, myH,
          75, 43, 30, 255
          );
    }
  }

  if(_flag_showGraph){
    /// Dibujamos los triangulos
    pair<float, float> point1, point2;
    for(i=0, n=triangles.size(); i<n; i++){
      for(j=0; j<3; j++){
        point1 = (*triangles[i])[j];
        point2 = (*triangles[i])[(j+1)%3];
        lineRGBA(Surf_Display,
                fixX(point1.first), fixY(point1.second),
                fixX(point2.first), fixY(point2.second),
                20, 20, 20, 40
            );
      }
    }
    /// Dibujamos el grafo
    nodes = graph.getNodes();
    for(i=0, n=nodes.size(); i<n; i++){
      node = nodes[i];

      // Intensidad del olor
      if( (n2 = node->olores.size()) ){
        int _w = 50, _h = _w/2;
        float _normal = float(n2)/60.0f;
        filledEllipseRGBA(Surf_Display,
            fixX((*node)[0]), fixY((*node)[1]),
            min(int(_normal*_w + widthNode), _w),
            min(int(_normal*_h + widthNode/2), _h),
            240, 160, 0, 200
            );
      }

      // Nodo como tal
      filledEllipseRGBA(Surf_Display,
          fixX((*node)[0]), fixY((*node)[1]),
          widthNode, widthNode/2,
          40, 40, 120, 240
          );

      // Aristas que salen de el
      vector<Node *>neighbors = node->getNeighbors();
      for(j=0, n2=neighbors.size(); j<n2; j++){
        lineRGBA(Surf_Display,
            fixX((*node)[0]), fixY((*node)[1]),
            fixX((*neighbors[j])[0]), fixY((*neighbors[j])[1]),
            80, 80, 120, 240
            );
      }
    }
    kine = player->getKinematic();
    Node *mynode = theApp.findNearestNode(kine->position);
    delete kine;
    filledEllipseRGBA(Surf_Display,
        fixX((*mynode)[0]), fixY((*mynode)[1]),
        18, 9,
        0, 0, 0, 80
        );

    // Nodo mas cercano a los personajes
    for(i=0, n=characters.size(); i<n; i++ ){
        kine = characters[i]->getKinematic();
        mynode = theApp.findNearestNode(kine->position);
        filledEllipseRGBA(Surf_Display,
            fixX((*mynode)[0]), fixY((*mynode)[1]),
            10, 6,
            20, 20, 200, 128
            );
        delete kine;
    }
  }
  /// Personajes del escenario
  for(i=0, n=characters.size(); i<n; i++ )
      characters[i]->OnRender(Surf_Display);

  for(i=0, n=enemy.size(); i<n; i++ )
      enemy[i]->OnRender(Surf_Display);

  player->OnRender(Surf_Display);


  // Olores
  nodes = graph.getNodes();
  for(i=0, n=nodes.size(); i<n; i++){
    node = nodes[i];

    for(j=0; j<(int)node->olores.size(); j++){
      Olor* olor = node->olores[j];

      if(theApp._flag_showOlor){
        olor->OnRender(Surf_Display);
      }
    }
  }

  for(i=0, n=gritos.size(); i<n; i++){
    gritos[i]->OnRender(Surf_Display);
  }

//// DEBUG
//filledEllipseRGBA(Surf_Display,
//    fixX((int)myCurrent[0]), fixY((int)myCurrent[1]),
//    10, 6,
//    80, 80, 80, 220
//    );

  // Dibujamos la nave
  if (termino){
    for (int i =0,n = m.naves.size(); i < n;i++){
      m.naves[i]->OnRender(Surf_Display);
    }
  }
  SDL_Flip(Surf_Display);
}

Node *CApp::findNearestNode(float point[2]){
    int i, n;
    float min, len;
    float aux[3] = {0};
    Node *min_node;
    vector<Node *>nodes = graph.getNodes();
    Node *node = NULL;
    if(nodes.size() == 0) return NULL;
    min_node = nodes[0];
    restarVectores(nodes[0]->getPosition(), point, aux);
    min = length(aux);
    for(i=1, n=nodes.size(); i<n; i++){
        node = nodes[i];
        restarVectores(node->getPosition(), point, aux);
        len = length(aux);
        if(len < min){
            min = len;
            min_node = node;
        }
    }
    return min_node;
}


Character* CApp::enemyMasCercano(Kinematic* myK){
  float posRet[3] = {1000,1000,1000},aux[3];
  vector<Character*> enemys =m.GetEnemys();
  Character *ret = NULL;
  for (int cont = 0, n= enemys.size(); cont < n ; cont++ ){
    Kinematic *b = enemys[cont]->getKinematic();
    restarVectores(myK->position,b->position,aux);
//  if (abs(aux[0]) < 300 && abs(aux[1]) < 300){
    if (length(aux)< 300){
      if (length(aux) < length(posRet)){
//    if (ret == NULL || posRet[0] < abs(aux[0]) || posRet[1] < abs(aux[1])){
        posRet[0] = abs(aux[0]);
        posRet[1] = abs(aux[1]);
        ret = enemys[cont];
      }
    }
    delete (b);
  }
  return ret;
  
 
}

Graph *CApp::getGraph(){
    return &graph;
}

Character* CApp::GetPlayer(){
  return m.GetPlayer();
}

Platform* CApp::getPlatform(float p[3]){
  return  m.getPlatform(p);
}

Obstacle* CApp::getCollision(Character* c,
    float **current, float **I, float **N)
{
  int i, n;
  for(i=0, n=obstacle.size(); i<n; i++){
    // printf("    Vericicando %d\n", i);
    if(obstacle[i]->collideWith(c, current, I, N)){
      // DEBUG
//    printf("Colisiona :D\n");
//    printf("Normal: %.2f  %.2f\n", (*N)[0], (*N)[1]);
//    printf("Choco en: %.2f  %.2f\n", (*I)[0],(*I)[1]);
      myCurrent[0] = (*current)[0];
      myCurrent[1] = (*current)[1];
      myCurrent[2] = (*current)[2];

      return obstacle[i];
    }
  }

  myCurrent[0] = 0;
  myCurrent[1] = 0;
  myCurrent[2] = 0;

  return NULL;
}

void CApp::Yells(Kinematic *k){
  gritos.push_back(new Grito(k->position[0], k->position[1]));
  m.Yells(k);
}


vector<Character*> CApp::getEnemyNear(Kinematic *k){
  float aux[3];
  vector<Character*> ret;
  vector<Character*> enemys = m.GetEnemys();
  for (int i = 0, n = enemys.size(); i < n;i++){
    Kinematic * b= enemys[i]->getKinematic();
    restarVectores(k->position,b->position,aux);
    if (length(aux) < 50){
      ret.push_back(enemys[i]);
    }
    delete b;
  }
  return ret;
}


float fixX(float x){
  Kinematic* kine = theApp.GetPlayer()->getKinematic();
  float resp = (kine->position[0] - TAM_VENTANA_X/2);
  if(resp<0)
      resp = 0;
  else if(TAM_MAX_VENTANA_X-resp<TAM_VENTANA_X)
      resp = TAM_MAX_VENTANA_X-TAM_VENTANA_X;
  delete kine;
  return x - resp;
}

float fixY(float y){
  Kinematic* kine  = theApp.GetPlayer()->getKinematic();
  float resp = (kine->position[1] - 5*TAM_VENTANA_Y/8);
  if(resp<0)
      resp = 0;
  else if(TAM_MAX_VENTANA_Y-resp<TAM_VENTANA_Y)
      resp = TAM_MAX_VENTANA_Y-TAM_VENTANA_Y;
  delete kine;
  return y - resp;
}

Character* CApp::getEnemyToAttack(Kinematic *k){
  vector<Character*> enemys = m.GetEnemys();
  for (int i =0, n = enemys.size(); i < n ; i++){
    float aux[3];
    Kinematic *b = enemys[i]->getKinematic();
    restarVectores(k->position,b->position,aux);
    if (length(aux) < 250){
      return enemys[i];
    }
    delete b;
  }
  return NULL;
}


void CApp::PikmenComes(float p[3]){
  float aux[3];
  vector<Character*>characters = m.GetCharacters();
  for (int i = 0, n = characters.size(); i < n; i++){
    Kinematic *b = characters[i]->getKinematic();
    restarVectores(p,b->position,aux);
    if (length(aux)  < 400){
      characters[i]->changeToSeekPNow();
    }
  }
}

void CApp::PikmenFree(){
  vector<Character*>characters = m.GetCharacters();
  for (int i = 0, n = characters.size(); i < n; i++){
    characters[i]->getFree();
  }
}

Character * CApp::enemyCerca(Kinematic *myK){
  float aux[3];
  int best = 2000000, index = -1;
  vector<Character*> enemys = m.GetCharacters();
  enemys.push_back(m.GetPlayer());
  for (int cont = 0, n= enemys.size(); cont < n ; cont++ ){
    Kinematic *b = enemys[cont]->getKinematic();
    restarVectores(myK->position,b->position,aux);
    if (length(aux) < 200){
      if (length(aux) < best){
        best = length(aux);
        index = cont;
      }
    }
    delete (b);
  }
  if (index == -1)
    return NULL;
  return enemys[index];
}

void CApp::changeWind(){
  int radio = 10;
  wind_dir_x = rand()%radio*2-radio+5;
  wind_dir_y = rand()%radio*2-radio+5;

  // Cambiamos el intervalo de cambio de viento
  wind_dir_time = rand()%4000+3000;
}

bool CApp::enemyCercaB(Kinematic *myK){
  float aux[3];
  vector<Character*> enemys = m.GetCharacters();
  enemys.push_back(m.GetPlayer());
  for (int cont = 0, n= enemys.size(); cont < n ; cont++ ){
    Kinematic *b = enemys[cont]->getKinematic();
    restarVectores(myK->position,b->position,aux);
    if (abs(aux[0]) < 200 && abs(aux[1]) < 200){
      delete(b);
      return true;
    }
    delete (b);
  }
  return false;
}

void CApp::change(){
  vector<Character*> c = m.GetCharacters();
  for (int i =0, n =c.size(); i < n; i++){
    c[i]->pp();
  }
  for (int i = 0, n = naves.size();i < n; i++){
    naves[i]->follw =0;
  }
}

void CApp::atacar(Kinematic *myK){
  float aux[3];
  vector<Character*> c = m.GetCharacters();
  c.push_back(m.GetPlayer());
  for (int i =0, n = c.size(); i < n;i++){
    Kinematic *b = c[i]->getKinematic();
    restarVectores(myK->position,b->position,aux);
    if (length(aux) < 50 && 
      (abs(myK->position[2]-b->position[2]) <= 10)){
      c[i]->atacado();
    }
    delete (b);
  }
}
